package message.handler;

import message.entity.MsgConfig;
import message.utils.StringUtils;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class BaseHandler implements IMsgHandler {

    MsgConfig msgConfig;

    byte[] msgData;

    Map<String, Object> unPackResult;

    public byte[] pack(MsgConfig msgConfig, Map source) throws Exception {
        this.msgConfig = msgConfig;
        packInit();
        doPack(source);
        return getMsgData();
    }

    public Map<String, Object> unPack(MsgConfig msgConfig, byte[] rspByte) throws Exception {
        this.msgConfig = msgConfig;
        unPackInit();
        doUnPack(rspByte);
        return getUnPackResult();
    }

    protected abstract void doPack(Map source) throws Exception;

    protected abstract void doUnPack(byte[] rspByte) throws Exception;


    protected void packDataToMap(Element element, Map data, Map source) {
        String name = element.getName();
        Map<String, String> elementAttr = getElementAttr(element);  // 属性
        List<Element> elements = element.elements();    // 子节点
        if (name.equals("Document")) {
            elements.forEach((ele) -> {
                packDataToMap(ele, data, source);
            });
        }
        if (name.equals("Field")) {
            packFiled(elementAttr, elements, data, source);
        }
        if (name.equals("Map")) {
            packMap(elementAttr, elements, data, source);
        }
        if (name.equals("List")) {
            packList(elementAttr, elements, data, source);
        }
    }

    protected void unPackDataToMap(Element element, Map data, Map source) {
        String name = element.getName();
        Map<String, String> elementAttr = getElementAttr(element);  // 属性
        List<Element> elements = element.elements();    // 子节点
        String attrName = elementAttr.get("name");
        if (name.equals("Document")) {
            elements.forEach((ele) -> {
                unPackDataToMap(ele, data, source);
            });
        }
        if (name.equals("Field")) {
            unPackFiled(elementAttr, data, source);
        }
        if (name.equals("Map")) {
            unPackMap(elementAttr, elements, data, source);
        }
        if (name.equals("List")) {
            unPackList(elementAttr, elements, data, source);
        }
    }

    protected Map<String, String> getElementAttr(Element element) {
        Map<String, String> attributes = new HashMap<>();
        List<Attribute> attrs = element.attributes();
        attrs.forEach(attr -> {
            attributes.put(attr.getName(), attr.getValue());
        });
        return attributes;
    }

    protected Object getValueForSource(String expr, Map source) {
        if (expr == null || expr.isEmpty()) {
            return "";
        }
        String[] split = expr.split("\\.");
        Object data = source;
        for (String key : split) {
            if (data != null && data instanceof Map) {
                data = ((Map) data).get(key);
            }
        }
        return data;
    }

    protected void putValue(String expr, Object value, Map<String, Object> data) {
        if (expr == null || expr.isEmpty()) {
            return;
        }
        String[] split = expr.split("\\.");
        if (split.length == 1) {
            data.put(split[0], value);
            return;
        }
        Map currentMap = data;
        for (int i = 0; i < split.length - 1; i++) {
            String key = split[i];
            Map childMap;
            if (!currentMap.containsKey(key)) {
                childMap = new HashMap<>();
            } else {
                Object o = currentMap.get(key);
                childMap = o instanceof Map ? (Map) o : new HashMap();
            }
            currentMap.put(key, childMap);
            currentMap = childMap;
        }
        currentMap.put(split[split.length - 1], value);

    }

    protected byte[] getMsgData() {
        return this.msgData;
    }

    protected Map<String, Object> getUnPackResult() {
        return this.unPackResult;
    }

    protected void packFiled(Map<String, String> elementAttr, List<Element> elements, Map data, Map source) {
        data.put(elementAttr.get("name"),
                getValueForSource(elementAttr.get("expression"), source));
    }

    protected void packMap(Map<String, String> elementAttr, List<Element> elements, Map data, Map source) {
        Map childData = new HashMap();
        data.put(elementAttr.get("name"), childData);
        elements.forEach((ele) -> {
            packDataToMap(ele, childData, source);
        });
    }

    protected void packList(Map<String, String> elementAttr, List<Element> elements, Map data, Map source) {
        List childList = new ArrayList();
        data.put(elementAttr.get("name"), childList);
        Object childSource = getValueForSource(elementAttr.get("expression"), source);
        if (!(childSource instanceof List)) {
            return;
        }
        ((List) childSource).forEach(oneChildSource -> {
                    Map sunData = new HashMap();
                    elements.forEach((ele) -> {
                        packDataToMap(ele, sunData, (Map) oneChildSource);
                    });
                    childList.add(sunData);
                }
        );
    }

    protected void unPackFiled(Map<String, String> elementAttr, Map data, Map source) {
        unPackFiled(elementAttr, data, source, null);
    }

    protected void unPackFiled(Map<String, String> elementAttr, Map data, Map source, List<Element> elements) {
        Object result = source.get(elementAttr.get("name"));
        if (null != elements && result instanceof String) {
            result = invokeMethod(elements, result);
        }
        putValue(elementAttr.get("expression"), result, data);
    }

    protected void unPackMap(Map<String, String> elementAttr, List<Element> elements, Map data, Map source) {
        Map currentSource = null;
        if (source != null) {
            Object childSource = source.get(elementAttr.get("name"));
            currentSource = childSource == null || !(childSource instanceof Map) ? new HashMap<>() : (Map) childSource;
        }
        for (int i = 0; i < elements.size(); i++) {
            unPackDataToMap(elements.get(i), data, currentSource);
        }
    }

    protected void unPackList(Map<String, String> elementAttr, List<Element> elements, Map data, Map source) {
        List childDataList = new ArrayList();
        putValue(elementAttr.get("expression"), childDataList, data);
        Object childSource = getValueForSource(elementAttr.get("name"), source);
        if (!(childSource instanceof List) || ((List) childSource).size() == 0) {
            return;
        }
        ((List) childSource).forEach(oneChildSource -> {
                    Map sunData = new HashMap();
                    elements.forEach((ele) -> {
                        unPackDataToMap(ele, sunData, (Map) oneChildSource);
                    });
                    childDataList.add(sunData);
                }
        );
    }

    protected Object invokeMethod(List<Element> elements, Object value) {
        Object result = value;
        for (int i = 0; i < elements.size(); i++) {
            Element element = elements.get(i);
            String name = element.getName();
            if ("Method".equals(name)) {
                Map<String, String> elementAttr = getElementAttr(element);
                result = invoke(elementAttr, result);
            }
        }
        return result;
    }

    protected Object invoke(Map<String, String> elementAttr, Object value) {
        String methodName = elementAttr.get("name");
        int currentLength = value.toString().length();
        int targetLength = Integer.parseInt(elementAttr.get("length"));
        int fullLength = targetLength - currentLength;
        String methodValue = elementAttr.get("value");
        char fullValue = methodValue.toCharArray()[0];
        Object result = value;
        if (methodName.equals("rightFull")) {
            result = StringUtils.rightFill((String) value, fullLength, fullValue, null);
        }
        if (methodName.equals("leftFull")) {
            result = StringUtils.leftFill((String) value, fullLength, fullValue, null);
        }
        if (methodName.equals("charDel")) {
            result = ((String) value).substring(0, targetLength);
        }
        return result;
    }
}
